package com.saascloud.log.core;

import cn.hutool.core.collection.CollectionUtil;
import com.saascloud.aop.AopOrder;
import com.saascloud.core.SessionContext;
import com.saascloud.core.pojo.User;
import com.saascloud.thread.ThreadTaskUtils;
import com.saascloud.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;

/**
 * @Author: liangxu
 * @Contact: one_dream@aliyun.com
 * @Date: 2022/5/19 23:15
 * @Version: 1.0
 * @Description:
 */
@Aspect
@Slf4j
@Order(AopOrder.LOG_ORDER)
public class LogAspect {
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();

    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    private static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext();


    private SessionContext sessionContext;

    @Autowired(required = false)
    private List<LogEventListener> logEventListeners;

    public LogAspect(SessionContext sessionContext) {
        this.sessionContext = sessionContext;
    }

    @Pointcut("@annotation(com.isoftstone.common.log.core.SysLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        String className = point.getTarget().getClass().getSimpleName();
        String methodName = point.getSignature().getName();
//        log.info("方法：[{}.{}]开始执行", className, methodName);
        Object[] args = point.getArgs();//2.传参  
//        for (Object object : args) {
//            log.info("传入参数:[{}]", object);
//        }
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
//        log.info("方法：[{}.{}]结束执行", className, methodName);
        //执行时长(毫秒)
        long endTime = System.currentTimeMillis();
        long time = endTime - beginTime;
        Object resultInfo = (result + "").length() > 0 && (result + "").length() <= 2048 ? result : "返回结果太长不打印";
        log.info("【{}.{}】>>>>入参:>>>>【{}】--结果:>>>>【{}】>>>>耗时:【{}】ms", className, methodName, args, !ObjectUtils.isEmpty(result) ? resultInfo : "空", time);
        return result;
    }

    public void publishLog(ProceedingJoinPoint point) {
        if (CollectionUtil.isEmpty(logEventListeners)) {
            log.debug("没有操作日志事件监听者,忽略...");
            return;
        }
        LogEvent.LogEventBuilder logBuilder = LogEvent.builder();
        logBuilder.opTime(new Date());
        if (sessionContext != null) {
            User user = sessionContext.getUser();
            logBuilder.user(user);
        }
        try {
            Object[] arguments = point.getArgs();
            String[] params = DISCOVERER.getParameterNames(getMethod(point));
            StandardEvaluationContext context = new StandardEvaluationContext();

            if (params != null && params.length > 0) {
                for (int len = 0; len < params.length; len++) {
                    context.setVariable(params[len], arguments[len]);
                }
            }
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            SysLog sysLog = method.getAnnotation(SysLog.class);
            logBuilder.action(sysLog.act());
            Expression expression = PARSER.parseExpression(sysLog.log(), TEMPLATE_PARSER_CONTEXT);
            String logContent = expression.getValue(context, String.class);
            logBuilder.logContent(logContent);
        } catch (Exception e) {
            logBuilder.logContent("操作日志解析异常");
        } finally {
            ThreadTaskUtils.execute(new Runnable() {
                @Override
                public void run() {
                    LogEvent logEvent = logBuilder.build();
                    try {
                        for (LogEventListener logEventListener : logEventListeners) {
                            logEventListener.publish(logEvent);
                        }
                    } catch (Exception e) {
                        log.warn("发布日志时间失败,{}", JsonUtils.obj2String(logEvent), e);
                    }
                }
            });
        }
    }


    /**
     * 根据切点解析方法信息
     *
     * @param joinPoint 切点信息
     * @return Method 原信息
     */
    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                        method.getParameterTypes());
            } catch (SecurityException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return method;
    }

    public static void main(String[] args) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        User user = new User();
        user.setUserName("aaaa");
        context.setVariable("user", user);
        context.setVariable("age", 18);
        Expression expression = new SpelExpressionParser().parseExpression("修改#{#user.userName}年纪为#{#age}", new TemplateParserContext("#{", "}"));
        System.out.println("解析结果：" + expression.getValue(context, String.class));
    }

}
